package com.xyy.saas.payment.payment.core.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import com.xyy.ec.base.framework.rpc.ApiRPCResult;
import com.xyy.ec.order.api.pay.PingAnAccountApi;
import com.xyy.ec.order.api.pay.PingAnCreditMerchantApi;
import com.xyy.ec.order.api.query.OrderApi;
import com.xyy.ec.order.business.api.OrderBusinessApi;
import com.xyy.ec.order.dto.pay.OrderTransDto;
import com.xyy.ec.order.dto.pay.XydMerchantBaseInfoDto;
import com.xyy.ec.order.dto.pay.XydOrderInfoDto;
import com.xyy.saas.payment.adpater.pingxx.domain.PayDO;
import com.xyy.saas.payment.adpater.pingxx.domain.RefundDO;
import com.xyy.saas.payment.adpater.xydLoan.core.constants.XydPharmacyTypeEnum;
import com.xyy.saas.payment.adpater.xydLoan.core.dto.*;
import com.xyy.saas.payment.adpater.xydLoan.core.enums.XydLoanResultStatus;
import com.xyy.saas.payment.adpater.xydLoan.core.enums.XydNotifyRepaymentOptStatus;
import com.xyy.saas.payment.adpater.xydLoan.core.req.*;
import com.xyy.saas.payment.adpater.xydLoan.core.resp.XydMerchantInfoSyncResp;
import com.xyy.saas.payment.adpater.xydLoan.core.resp.XydOrderValidVerifyResp;
import com.xyy.saas.payment.adpater.xydLoan.service.XydLoanService;
import com.xyy.saas.payment.callback.core.dto.CallbackMsg;
import com.xyy.saas.payment.callback.core.result.CallbackRunner;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.RocketMqTag;
import com.xyy.saas.payment.common.RocketMqTopic;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.common.pool.NamedTask;
import com.xyy.saas.payment.common.pool.PoolUtil;
import com.xyy.saas.payment.cores.constants.CallbackConstants;
import com.xyy.saas.payment.cores.enums.*;
import com.xyy.saas.payment.cores.param.Pair;
import com.xyy.saas.payment.cores.vo.CallbackResultVo;
import com.xyy.saas.payment.cores.vo.CreditNotifyApplyStatusVo;
import com.xyy.saas.payment.dao.mapper.PayResultOrderMapper;
import com.xyy.saas.payment.dao.mapper.RefundOrderMapper;
import com.xyy.saas.payment.dao.mapper.XydRepayPlanDetailMapper;
import com.xyy.saas.payment.dao.mapper.XydRepayPlanMapper;
import com.xyy.saas.payment.dao.model.*;
import com.xyy.saas.payment.merchant.core.dto.RechargeHandleDto;
import com.xyy.saas.payment.payment.core.service.PrePayOrderService;
import com.xyy.saas.payment.payment.core.service.RefundOrderService;
import com.xyy.saas.payment.payment.core.service.XydCreditLoanService;
import com.xyy.saas.payment.service.PayResultOrderService;
import com.xyy.saas.payment.service.PinganLoanFinanceApplyService;
import com.xyy.saas.payment.trace.AlarmService;
import com.xyy.saas.payment.trace.DingTalkMessage;
import com.xyy.saas.payment.util.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wangzeqi
 */
@Slf4j
@Service
public class XydCreditLoanServiceImpl implements XydCreditLoanService {
    @Resource
    private PinganLoanFinanceApplyService pinganLoanFinanceApplyService;
    @Autowired
    private CallbackRunner callbackRunner;
    @Resource
    private RedisUtil redisUtil;
    @Autowired
    private LeafUtil leafUtil;
    @Reference(version = "1.0.0")
    private OrderApi orderApi;
    @Reference(version = "1.0.0")
    private PingAnCreditMerchantApi pingAnCreditMerchantApi;
    @Reference(version = "1.0.0")
    private PingAnAccountApi pingAnAccountApi;
    @Autowired
    private PayResultOrderService payResultOrderService;
    @Autowired
    private PrePayOrderService prePayOrderService;
    @Autowired
    private RefundOrderService refundOrderService;
    @Autowired
    private PayResultOrderMapper payResultOrderMapper;
    @Autowired
    private RefundOrderMapper refundDomainMapper;
    @Autowired
    private XydRepayPlanDetailMapper repayPlanDetailMapper;
    @Autowired
    private XydRepayPlanMapper repayPlanMapper;
    @Autowired
    private XydLoanService xydLoanService;
    @Reference(version = "1.0.0")
    private OrderBusinessApi orderBusinessApi;

    //不可用授信状态类型集合
    private final Set<String> unAvailableCreditStatusSet = Sets.newHashSet(XydCreditStatusEnum.REFUSE.getCode(), XydCreditStatusEnum.FREEZE.getCode());

    //授信操作-授信结果状态集合
    private final Set<String> increaseCreditTypeSet = Sets.newHashSet(XydCreditStatusEnum.PASS.getCode(), XydCreditStatusEnum.REFUSE.getCode(), XydCreditStatusEnum.PROCESSING.getCode());

    //贷中额度调整-授信结果状态集合
    private final Set<String> loaningManagerSet = Sets.newHashSet(XydCreditStatusEnum.FREEZE.getCode(), XydCreditStatusEnum.ACTIVE.getCode(),
            XydCreditStatusEnum.UN_FREEZE.getCode(), XydCreditStatusEnum.DOWN_GRADE.getCode());
    @Autowired
    private AlarmService alarmService;

    @SneakyThrows
    @Override
    public boolean notifyAuthCredit(XydNotifyAuthCreditDto creditDto) {
        log.info("小雨点 notifyAuthCredit {}", JSONObject.toJSONString(creditDto));
        if (creditDto == null || !creditDto.checkParam()) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

        String key = RedisUtil.XYD_RESULT_NOTIFY + creditDto.getOuterOrderNo();
        if (!redisUtil.setNx(key, "1", 10)) {
            log.warn("小雨点 notifyAuthCredit get lock fail {}", JSONObject.toJSONString(creditDto));
            throw new PaymentException(ResultCodeEnum.PROFIT_SHARING_REPEAT_REQUEST);
        }

        try {
            PayFinanceApply payFinanceApply = pinganLoanFinanceApplyService.selectByThirdApplyNo(creditDto.getOuterOrderNo());
            if (payFinanceApply == null) {
                log.error("小雨点 notifyAuthCredit payFinanceApply is null, param:{}", JSON.toJSONString(creditDto));
                throw new PaymentException("-1", "未查到有效申请记录");
            }

            //额度变化类型
            String creditChangeType = creditDto.getCreditChangeType();
            //授信结果
            String creditStatus = creditDto.getCreditStatus();

            if (Objects.equals(creditChangeType, XydCreditTypeEnum.INCREASE_CREDIT.getCode())) {
                if (!increaseCreditTypeSet.contains(creditStatus)) {
                    throw new PaymentException("-1", "小雨点 授信操作-授信结果无效");
                }
            } else if (Objects.equals(creditChangeType, XydCreditTypeEnum.LOANING_MANAGER.getCode())) {
                if (!loaningManagerSet.contains(creditStatus)) {
                    throw new PaymentException("-1", "小雨点 贷中额度调整操作-授信结果无效");
                }
            } else {
                throw new PaymentException("-1", "小雨点 通知授信结果数据-额度变化类型无效");
            }

            //“处理中”状态暂不做任何处理
            if (creditStatus.equals(XydCreditStatusEnum.PROCESSING.getCode())) {
                return true;
            }

            // 拒绝 or 冻结，不可用
            if (unAvailableCreditStatusSet.contains(creditStatus)) {
                payFinanceApply.setDenyReason(creditDto.getStatusMsg() + payFinanceApply.getDenyReason());
                payFinanceApply.setApplyStatus(PinganLoanApplyStatusEnum.getCodeByXydCreditStatus(creditStatus));
            } else {
                payFinanceApply.setContractNo(creditDto.getApplicationNo());
                payFinanceApply.setApplyStatus(PinganLoanApplyStatusEnum.SIGNED_CONT_SUCCESS.getCode());
                payFinanceApply.setAmountStartDate(DateUtil.localDateTimeFormat(LocalDateTime.now()));
                payFinanceApply.setAmountEndDate(creditDto.getFailureTime().substring(0, 10).replace("-", ""));
                payFinanceApply.setApproveAmount(AmountCastUtil.formatBigDecimal2PercentInt(creditDto.getCreditAmount()));
                payFinanceApply.setDenyReason(creditDto.getStatusMsg() + payFinanceApply.getDenyReason());
                payFinanceApply.setDayRate(convertToDayRate(creditDto.getRepaymentTypes()));
            }

            log.info("小雨点 notifyAuthCredit payFinanceApply update creditDto:{}==== payFinanceApply:{}", JSONObject.toJSONString(creditDto), JSONObject.toJSONString(payFinanceApply));
            pinganLoanFinanceApplyService.updateByPrimaryKeySelective(payFinanceApply);

            CreditNotifyApplyStatusVo xydNotifyApplyStatusVo = po2Vo(payFinanceApply);
            //可用额度
            xydNotifyApplyStatusVo.setAvailableAmount(AmountCastUtil.formatBigDecimal2PercentInt(creditDto.getAvailableCreditAmount()));

            // 回调EC
            CallbackMsg callbackMsg = new CallbackMsg();
            callbackMsg.setCallback(CallbackConstants.XYD_CALLBACK_GROUP);
            callbackMsg.setType(CallbackConstants.APPLY_STATUS);
            callbackMsg.setMsg(creditDto.getStatusMsg());
            callbackMsg.setExtend(JSON.toJSONString(xydNotifyApplyStatusVo));
            log.info("小雨点 notifyAuthCredit callback creditDto:{}==== callbackMsg:{}", JSONObject.toJSONString(creditDto), JSONObject.toJSONString(callbackMsg));
            callbackRunner.callback(callbackMsg);
        } catch (Exception e) {
            log.error("###小雨点授信通知异常, param:{}", JSONObject.toJSONString(creditDto), e);

            alarmService.asyncAlarm("小雨点授信通知异常", "notifyAuthCredit",
                    e.getMessage(), JSONObject.toJSONString(creditDto));

        }
        return true;
    }

    private String convertToDayRate(String repaymentTypes) {
        if (StringUtils.isNotBlank(repaymentTypes)) {
            // 目前只接 随借随还-到期还本付息
            String listStr = JSON.parseObject(repaymentTypes).getString("3");
            if (StringUtils.isNotBlank(listStr)) {
                List<Map> jsonArray = JSON.parseArray(listStr, Map.class);
                if (jsonArray != null && !jsonArray.isEmpty()) {
                    Object apr = jsonArray.get(0).get("apr");
                    if (apr != null) {
                        return new BigDecimal(apr.toString().replace("%", "")).divide(BigDecimal.valueOf(100)).stripTrailingZeros().toPlainString();
                    }
                }
            }
        }
        return null;
    }

    private CreditNotifyApplyStatusVo po2Vo(PayFinanceApply payFinanceApply) {
        CreditNotifyApplyStatusVo statusVo = new CreditNotifyApplyStatusVo();
        statusVo.setThirdApplyNo(payFinanceApply.getThirdApplyNo());
        statusVo.setUserId(payFinanceApply.getBusinessUserId());
        statusVo.setContractNo(payFinanceApply.getContractNo());
        statusVo.setApplyStatus(payFinanceApply.getApplyStatus());
        statusVo.setAmountStartDate(payFinanceApply.getAmountStartDate());
        statusVo.setAmountEndDate(payFinanceApply.getAmountEndDate());
        statusVo.setApproveAmount(payFinanceApply.getApproveAmount());
        statusVo.setDayRate(payFinanceApply.getDayRate());
        statusVo.setCreditChannel(FinanceChannelEnum.XYD_LOAN.getCode());
        return statusVo;
    }

    @Override
    public List<String> caseMethod(List<Pair> params) {
        log.info("caseMethod param:{}", JSON.toJSONString(params));
        return Arrays.asList("data1", "data2");
    }

    @SneakyThrows
    @Override
    public XydMerchantInfoSyncResp merchantInfoSync(XydMerchantInfoSyncReq xydMerchantInfoSyncReq) {
        log.info("小雨点 merchantInfoSync query");
        XydMerchantInfoSyncResp xydMerchantInfoSyncResp = new XydMerchantInfoSyncResp();
        try {
            XydMerchantBaseInfoDto merchantBaseInfoDto = pingAnCreditMerchantApi.queryByMerchantIdAndCreditChannel(Long.valueOf(xydMerchantInfoSyncReq.getThirdUserNo()), FinanceChannelEnum.XYD_LOAN.getCode());
            log.info("小雨点 merchantInfoSync merchantBaseInfoDto:{}", JSONObject.toJSONString(merchantBaseInfoDto));
            if (merchantBaseInfoDto != null) {
                merchantBaseInfoDto.setPharmacyType(XydPharmacyTypeEnum.getXydCodeStr(Integer.parseInt(merchantBaseInfoDto.getPharmacyType())));
                xydMerchantInfoSyncResp.setBaseEnterpriseInfo(merchantBaseInfoDto);
            }
            log.info("小雨点 merchantInfoSync xydMerchantInfoSyncResp:{}", JSONObject.toJSONString(xydMerchantInfoSyncResp));

        } catch (Exception e) {
            log.error("小雨点 merchantInfoSync error，param thirdUserNo:{}，channel:{}", xydMerchantInfoSyncReq.getThirdUserNo(), FinanceChannelEnum.XYD_LOAN.getCode(), e);

            alarmService.asyncAlarm("同步客户数据(申请授信小雨点反查)异常", "merchantInfoSync",
                    e.getMessage(), xydMerchantInfoSyncReq.getThirdUserNo());

        }
        return xydMerchantInfoSyncResp;
    }

    @SneakyThrows
    @Override
    public List<XydOrderInfoDto> historyOrderInfoSync(XydHistoryOrderInfoSyncReq xydHistoryOrderInfoSyncReq) {
        try {
            log.info("小雨点 historyOrderInfoSync xydHistoryOrderInfoSyncReq:{}", JSONObject.toJSONString(xydHistoryOrderInfoSyncReq));
            ApiRPCResult<List<XydOrderInfoDto>> apiRPCResult = orderApi.queryXydOrderInfoByMerchantId(xydHistoryOrderInfoSyncReq.getThirdUserNo());
            log.info("小雨点 historyOrderInfoSync apiRPCResult:{}", JSONObject.toJSONString(apiRPCResult));
            if (apiRPCResult != null && apiRPCResult.isSuccess()) {
                return apiRPCResult.getData();
            }
        } catch (Exception e) {
            log.error("小雨点查询历史订单数据异常， merchantId:{}", xydHistoryOrderInfoSyncReq.getThirdUserNo(), e);

            alarmService.asyncAlarm("同步历史订单数据(申请授信小雨点反查)", "historyOrderInfoSync",
                    e.getMessage(), xydHistoryOrderInfoSyncReq.getThirdUserNo());
        }

        return Collections.emptyList();
    }

    @SneakyThrows
    @Override
    public XydOrderValidVerifyResp orderValidVerify(XydOrderValidVerifyReq xydOrderValidVerifyReq) {
        XydOrderValidVerifyResp xydOrderValidVerifyResp = new XydOrderValidVerifyResp();
        try {
            log.info("小雨点 orderValidVerify xydOrderValidVerifyReq:{}", JSONObject.toJSONString(xydOrderValidVerifyReq));
            ApiRPCResult<Boolean> validFlag = orderApi.isValidFlag(xydOrderValidVerifyReq.getThirdOrderNo());
            log.info("小雨点 orderValidVerify validFlag:{}", JSONObject.toJSONString(validFlag));
            if (validFlag != null && validFlag.isSuccess()) {
                xydOrderValidVerifyResp.setEffective(validFlag.getData());
            }
        } catch (Exception e) {
            log.error("小雨点查询订单是否有效异常， orderNo:{}", xydOrderValidVerifyReq.getThirdOrderNo(), e);

            alarmService.asyncAlarm("小雨点校验订单有效异常", "orderValidVerify",
                    e.getMessage(), xydOrderValidVerifyReq.getThirdOrderNo());

        }
        return xydOrderValidVerifyResp;
    }

    @SneakyThrows
    @Override
    public boolean notifyLoanResult(XydNotifyLoanResultReq xydNotifyLoanResultReq) {
        log.info("小雨点通知放款结果 notifyLoanResult={}", JSONObject.toJSONString(xydNotifyLoanResultReq));

        try {
            // 参数校验
            checkNotifyLoanResultParam(xydNotifyLoanResultReq);
            // 状态校验
            XydLoanResultStatus xydLoanResultStatus = XydLoanResultStatus.fromStatus(xydNotifyLoanResultReq.getLoanStatus());
            if (xydLoanResultStatus == null) {
                log.error("小雨点通知放款结果 放款状态值错误, {}-{}", xydNotifyLoanResultReq.getThirdOrderNo(), xydNotifyLoanResultReq.getLoanStatus());
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "放款状态值错误");
            }

            // 只处理成功和失败
            if (xydLoanResultStatus != XydLoanResultStatus.SUCCESSED && xydLoanResultStatus != XydLoanResultStatus.FAILED) {
                log.info("小雨点通知放款结果 notifyLoanResult not support status:{}, {}", xydLoanResultStatus, xydNotifyLoanResultReq.getThirdOrderNo());
                return true;
            }

            String key = RedisUtil.XYD_LOAN_PAY_NOTIFY + xydNotifyLoanResultReq.getThirdOrderNo();
            if (!redisUtil.setNx(key, "1", 10)) {
                log.info("小雨点通知放款结果 notifyLoanResult get lock fail notifyLoanResult={}", JSONObject.toJSONString(xydNotifyLoanResultReq));
                return true;
            }

            // 查询预支付订单
            PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(xydNotifyLoanResultReq.getThirdOrderNo());
            if (prePayOrder == null) {
                log.error("小雨点通知放款结果 notifyLoanResult order not exit error notifyLoanResult:{}", JSONObject.toJSONString(xydNotifyLoanResultReq));
                throw new PaymentException(ResultCodeEnum.ORDER_NOT_EXIST, "预支付订单不存在");
            }

            // 幂等判断
            PayResultOrder payResultOrder = payResultOrderService.selectByPayNoAndChannel(xydNotifyLoanResultReq.getThirdOrderNo(), Constants.CHANNEL_XYD_LOAN);
            if (payResultOrder != null) {
                log.warn("小雨点通知放款结果 notifyLoanResult payResultOrder is not null notifyLoanResult:{}", JSONObject.toJSONString(xydNotifyLoanResultReq));
                return true;
            }

            //支付失败直接通知业务方
            if (xydLoanResultStatus == XydLoanResultStatus.FAILED) {
                // 异步回调业务
                String topic = getPaySuccessNotifyTopic(prePayOrder.getBusinessType(), prePayOrder.getBusinessOrderType());

                CallbackMsg callbackMsg = new CallbackMsg();
                callbackMsg.setTradeNo(prePayOrder.getPayNo());
                callbackMsg.setType(CallbackResultVo.PAY_FAIL);
                callbackMsg.setAmount(prePayOrder.getAmount());
                callbackMsg.setCallback(prePayOrder.getCallbackAddress());
                callbackMsg.setNotifyTime(DateUtil.now());
                log.info("小雨点通知放款结果 notifyLoanResult callback topic:{} callbackMsg:{} notifyLoanResult:{}", topic, JSONObject.toJSONString(callbackMsg), JSONObject.toJSONString(xydNotifyLoanResultReq));
                callbackRunner.callback(topic, callbackMsg);
                return true;
            }

            PayDO payDO = notifyLoanResultBuildPayDo(prePayOrder, xydNotifyLoanResultReq);

            // 检验订单金额
            if (!payDO.getAmount().equals(prePayOrder.getAmount())) {
                // 回调进来的金额和发起订单金额不等
                log.warn("小雨点通知放款结果 notifyLoanResult 回调进来的金额和发起订单金额不等 {}-{} notifyLoanResult:{}", payDO.getAmount(),
                        prePayOrder.getAmount(),
                        JSONObject.toJSONString(xydNotifyLoanResultReq));
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "金额不一致");
            }

            callbackRunner.callbackPingXX(payDO);

            PoolUtil.ALARM_POOL.execute(NamedTask.createRun(() -> {
                try {
                    OrderTransDto orderTransDto = orderApi.queryOrderTransData(xydNotifyLoanResultReq.getThirdOrderNo()).getData();
                    repayPlanMapper.insert(buildInsertPlanPo(xydNotifyLoanResultReq, orderTransDto));
                } catch (Exception e) {
                    log.error("XydCreditLoanServiceImpl#notifyLoanResult error:{}", JSON.toJSONString(xydNotifyLoanResultReq), e);
                    alarmService.asyncAlarm("小雨点支付成功新增交易数据失败", JSON.toJSONString(xydNotifyLoanResultReq));
                }
            }, "小雨点支付成功新增还款计划" + xydNotifyLoanResultReq.getThirdOrderNo()));
            log.info("小雨点通知放款结果 notifyLoanResult end callbackPingXX payDO:{} notifyLoanResult:{}", JSONObject.toJSONString(payDO), JSONObject.toJSONString(xydNotifyLoanResultReq));
        } catch (Exception e) {
            log.error("###小雨点通知放款结果异常, param:{}", JSONObject.toJSONString(xydNotifyLoanResultReq));

            alarmService.asyncAlarm("小雨点通知放款结果异常", "notifyLoanResult",
                    e.getMessage(), JSON.toJSONString(xydNotifyLoanResultReq));

        }
        return true;
    }

    @Override
    public void addRepayPlanResult(XydNotifyLoanResultReq resultReq) {
        OrderTransDto orderTransDto = orderApi.queryOrderTransData(resultReq.getThirdOrderNo()).getData();
        repayPlanMapper.insert(buildInsertPlanPo(resultReq, orderTransDto));
    }

    private XydRepayPlanPo buildInsertPlanPo(XydLoanResultDto resultDto, OrderTransDto orderTransDto, PrePayOrder prePayOrder) {
        XydRepayPlanPo planPo = new XydRepayPlanPo();
        planPo.setOuterOrderNo(resultDto.getThirdOrderNo());
        planPo.setLoanNo(resultDto.getXydLoanNo());
        planPo.setOrderNo(orderTransDto.getOrderNo());
        planPo.setPayNo(orderTransDto.getPayNo());
        planPo.setLoanAmount(resultDto.getLoanAmount());
        planPo.setInterest(resultDto.getInterest());
        planPo.setCreditRate(NumberUtil.str2decimalZero(resultDto.getCreditRate(), 2));
        planPo.setLoanLimit(resultDto.getLoanLimit());
        planPo.setPayTime(prePayOrder.getCreateTime() == null ? new Date() : prePayOrder.getCreateTime());
        if (StringUtils.isNotEmpty(resultDto.getLoanTime())) {
            planPo.setLoanTime(DateUtil.parase(resultDto.getLoanTime(), DateUtil.DATEFORMATDAY));
        }
        List<XydRepaymentPlanDto> plans = resultDto.getPlans();
        if (CollUtil.isNotEmpty(plans)) {
            XydRepaymentPlanDto repaymentPlanDto = plans.get(0);
            if (StringUtils.isNotEmpty(repaymentPlanDto.getDueDate())) {
                planPo.setDueDate(DateUtil.parase(repaymentPlanDto.getDueDate(), DateUtil.DATEFORMATDAY));
            }
            if (StringUtils.isNotEmpty(repaymentPlanDto.getDueDateActual())) {
                planPo.setDueDateActual(DateUtil.parase(repaymentPlanDto.getDueDateActual(), DateUtil.DATEFORMATDAY));
            }
            planPo.setPaidOff(Boolean.TRUE.equals(repaymentPlanDto.getPaidOff()) ? 1 : 0);
            planPo.setTotalPlan(repaymentPlanDto.getInstallmentNumber());
            planPo.setPrincipalPaid(repaymentPlanDto.getPrincipalPaid());
            planPo.setPrincipal(repaymentPlanDto.getPrincipal());
            planPo.setCurrInterest(repaymentPlanDto.getInterest());
            planPo.setInterestPaid(repaymentPlanDto.getInterestPaid());
            planPo.setLateFee(repaymentPlanDto.getLateFee());
            planPo.setLateFeePaid(repaymentPlanDto.getLateFeePaid());
            planPo.setDayInDefault(repaymentPlanDto.getDayInDefault());
        }
        planPo.setOrgId(orderTransDto.getOrgId());
        planPo.setOrgName(orderTransDto.getCompanyName());
        planPo.setMerchantId(orderTransDto.getMerchantId());
        planPo.setMerchantName(orderTransDto.getMerchantName());
        planPo.setSubsidyInterest(BigDecimal.ZERO);
        planPo.setSubsidyDays(0);
        planPo.setRepayPenalty(BigDecimal.ZERO);
        planPo.setIsOriginalRoute(0);
        return planPo;
    }

    private XydRepayPlanPo buildInsertPlanPo(XydNotifyLoanResultReq resultReq, OrderTransDto orderTransDto) {
        XydRepayPlanPo planPo = new XydRepayPlanPo();
        planPo.setOuterOrderNo(resultReq.getOuterOrderNo());
        planPo.setLoanNo(resultReq.getXydLoanNo());
        planPo.setOrderNo(orderTransDto.getOrderNo());
        planPo.setPayNo(orderTransDto.getPayNo());
        planPo.setLoanAmount(resultReq.getLoanAmount());
        planPo.setInterest(resultReq.getInterest());
        planPo.setCreditRate(NumberUtil.str2decimalZero(resultReq.getCreditRate(), 2));
        planPo.setLoanLimit(resultReq.getLoanLimit());
        if (StringUtils.isNotEmpty(resultReq.getPayTime())) {
            planPo.setPayTime(DateUtil.parase(resultReq.getPayTime(), DateUtil.DATEFORMATSECOND));
        }
        if (StringUtils.isNotEmpty(resultReq.getLoanTime())) {
            planPo.setLoanTime(DateUtil.parase(resultReq.getLoanTime(), DateUtil.DATEFORMATDAY));
        }
        List<XydRepaymentPlanDto> plans = resultReq.getPlans();
        if (CollUtil.isNotEmpty(plans)) {
            XydRepaymentPlanDto repaymentPlanDto = plans.get(0);
            if (StringUtils.isNotEmpty(repaymentPlanDto.getDueDate())) {
                planPo.setDueDate(DateUtil.parase(repaymentPlanDto.getDueDate(), DateUtil.DATEFORMATDAY));
            }
            if (StringUtils.isNotEmpty(repaymentPlanDto.getDueDateActual())) {
                planPo.setDueDateActual(DateUtil.parase(repaymentPlanDto.getDueDateActual(), DateUtil.DATEFORMATDAY));
            }
            planPo.setPaidOff(Boolean.TRUE.equals(repaymentPlanDto.getPaidOff()) ? 1 : 0);
            planPo.setTotalPlan(repaymentPlanDto.getInstallmentNumber());
            planPo.setPrincipalPaid(repaymentPlanDto.getPrincipalPaid());
            planPo.setPrincipal(repaymentPlanDto.getPrincipal());
            planPo.setCurrInterest(repaymentPlanDto.getInterest());
            planPo.setInterestPaid(repaymentPlanDto.getInterestPaid());
            planPo.setLateFee(repaymentPlanDto.getLateFee());
            planPo.setLateFeePaid(repaymentPlanDto.getLateFeePaid());
            planPo.setDayInDefault(repaymentPlanDto.getDayInDefault());
        }
        planPo.setOrgId(orderTransDto.getOrgId());
        planPo.setOrgName(orderTransDto.getCompanyName());
        planPo.setMerchantId(orderTransDto.getMerchantId());
        planPo.setMerchantName(orderTransDto.getMerchantName());
        planPo.setSubsidyInterest(BigDecimal.ZERO);
        planPo.setSubsidyDays(0);
        planPo.setRepayPenalty(BigDecimal.ZERO);
        planPo.setIsOriginalRoute(0);
        return planPo;
    }

    private PayDO notifyLoanResultBuildPayDo(PrePayOrder prePayOrder, XydNotifyLoanResultReq xydNotifyLoanResultReq) {
        JSONObject extra = (StringUtils.isBlank(prePayOrder.getExtra()) ? new JSONObject() : JSONObject.parseObject(prePayOrder.getExtra()));
        // 基本参数
        PayDO payDO = new PayDO();
        payDO.setOrderNo(xydNotifyLoanResultReq.getThirdOrderNo());
//        payDO.setChannelChargeNo(jdNotifyTradeVo.getLoanNo());
        payDO.setAmount(AmountCastUtil.castYuan2Fen(xydNotifyLoanResultReq.getLoanAmount()));
        payDO.setChannel(Constants.CHANNEL_XYD_LOAN);
        payDO.setPayMode(extra.getString("payMode"));
        payDO.setResultCode("SUCCESS");
        payDO.setChannelTransactionNo(xydNotifyLoanResultReq.getXydLoanNo());
        payDO.setTimePaid(StringUtils.isBlank(xydNotifyLoanResultReq.getPayTime()) ? null : DateUtil.parase(xydNotifyLoanResultReq.getPayTime(), DateUtil.DATEFORMATSECOND));

        // 其他参数
        payDO.setNotifyTime(DateUtil.now());
        payDO.setCount(0);
        payDO.setExtra(prePayOrder.getExtra());
        payDO.setBusinessType(prePayOrder.getBusinessType());
        payDO.setBusinessOrderType(prePayOrder.getBusinessOrderType());
        payDO.setReceiverId(prePayOrder.getReceiverId());
        payDO.setCallbackAddress(prePayOrder.getCallbackAddress());
        payDO.setPayer(prePayOrder.getPayer());
        return payDO;
    }

    private void checkNotifyLoanResultParam(XydNotifyLoanResultReq xydNotifyLoanResultReq) throws PaymentException {
        if (StringUtils.isBlank(xydNotifyLoanResultReq.getLoanStatus()) ||
                StringUtils.isBlank(xydNotifyLoanResultReq.getThirdOrderNo()) ||
                // StringUtils.isBlank(xydNotifyLoanResultReq.getLoanAmount()) ||
                StringUtils.isBlank(xydNotifyLoanResultReq.getXydLoanNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
    }

    private String getPaySuccessNotifyTopic(String businessType, String businessOrderType) {
        try {
            // 1.根据产品线获取topic
            String topic = ApolloUtil.getProperty("paySuccess_notify_topic_" + businessOrderType);

            if (!org.apache.commons.lang.StringUtils.isBlank(topic)) {
                return topic;
            }

            // 2.根据业务线获取topic
            topic = ApolloUtil.getProperty("paySuccess_notify_topic_" + businessType);

            if (!org.apache.commons.lang.StringUtils.isBlank(topic)) {
                return topic;
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }

        return RocketMqTopic.TOPIC_CALLBACK;
    }

    @SneakyThrows
    @Override
    public boolean notifyLockOrder(XydNotifyLockOrderReq xydNotifyLockOrderReq) {
        return true;
    }

    @Override
    public boolean syncCreditNodeStatus(XydSyncCreditNodeStatusReq xydSyncCreditNodeStatusReq) {
        return true;
    }

    @Override
    @SneakyThrows
    public boolean cancelRepayment(XydNotifyCancelRepaymentDto repaymentDto) {
        log.info("cancelRepayment req:{}", JSON.toJSONString(repaymentDto));
        if (StringUtils.isEmpty(repaymentDto.getSerialNumber()) || StringUtils.isEmpty(repaymentDto.getThirdOrderNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        // 更新还款计划流水状态为已取消
        List<XydRepayPlanDetailPo> detailPos = repayPlanDetailMapper.selectByPayNo(repaymentDto.getThirdOrderNo());
        Map<String, XydRepayPlanDetailPo> flowNoMap = detailPos.stream().collect(Collectors.toMap(d -> d.getFlowNo(), d -> d, (o, n) -> o));
        XydRepayPlanDetailPo detailPo = flowNoMap.get(repaymentDto.getSerialNumber());
        if (detailPo == null) {
            throw new PaymentException("取消还款未查到有效支付还款记录" + repaymentDto.getSerialNumber());
        }
        PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(repaymentDto.getThirdOrderNo());
        if (prePayOrder == null || StringUtils.isBlank(prePayOrder.getPayer())) {
            throw new PaymentException(ResultCodeEnum.XYDLOAN_PAYER_ERROR, "通知类型错误" + repaymentDto.getThirdOrderNo());
        }

        XydRepayPlanDetailPo updateDetailPo = new XydRepayPlanDetailPo();
        updateDetailPo.setId(detailPo.getId());
        updateDetailPo.setStatus(XydStatusEnum.CANCEL.getCode());

        // 更新还款计划 借据字段
        XydLoanResultDto resultDto = xydLoanService.searchLoanResult(prePayOrder.getPayNo(), prePayOrder.getPayer());
        XydRepayPlanPo repayPlanPo = repayPlanMapper.selectByPayNo(prePayOrder.getPayNo());

        XydRepayPlanPo updatePlanPo = buildUpdatePlanPo(resultDto, repayPlanPo, flowNoMap);

        // 持久化数据
        repayPlanDetailMapper.updateByPrimaryKeySelective(updateDetailPo);
        repayPlanMapper.updateByPrimaryKeySelective(updatePlanPo);
        return true;
    }

    @Override
    public void initRepayPlan(List<String> payNoList) {
        if (CollUtil.isEmpty(payNoList)) {
            return;
        }

        for (String payNo : payNoList) {
            try {
                OrderTransDto orderTransDto = orderApi.queryOrderTransData(payNo).getData();
                PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(payNo);

                XydLoanResultDto resultDto = xydLoanService.searchLoanResult(prePayOrder.getPayNo(), prePayOrder.getPayer());
                repayPlanMapper.insert(buildInsertPlanPo(resultDto, orderTransDto, prePayOrder));
            } catch (Exception e) {
                log.error("initRepayPlan error payNo:{}", payNo, e);
            }
        }
    }

    @SneakyThrows
    @Override
    public XydOrderDetailInfoDto lendingOrderInfo(XydLendingOrderInfoReq xydLendingOrderInfoReq) {
        log.info("小雨点 lendingOrderInfo--获取订单详情信息入参：{}", JSONObject.toJSONString(xydLendingOrderInfoReq));

        try {
            PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(xydLendingOrderInfoReq.getThirdOrderNo());

            if (prePayOrder == null) {
                log.info("小雨点 lendingOrderInfo orderBusinessDto is null 入参：{}", JSONObject.toJSONString(xydLendingOrderInfoReq));
                return null;
            }
            Object custermerName = JSONUtils.getValueFromJson(prePayOrder.getExtra(), "custermerName");
            String orgName = custermerName == null ? null : custermerName.toString();
            XydOrderDetailInfoDto xydOrderDetailInfoDto = new XydOrderDetailInfoDto();
            xydOrderDetailInfoDto.setOrgCode(prePayOrder.getPayer());
            xydOrderDetailInfoDto.setOrgName(orgName);
            xydOrderDetailInfoDto.setOrderId(xydLendingOrderInfoReq.getThirdOrderNo());
            xydOrderDetailInfoDto.setOrderTime(DateUtil.date2String(prePayOrder.getCreateTime(), DateUtil.DATEFORMATSECOND));
            xydOrderDetailInfoDto.setOrderAmount(AmountCastUtil.castFen2Yuan(prePayOrder.getAmount()));
            //若订单为“未支付”状态，则回传“待付款”。
            xydOrderDetailInfoDto.setOrderStatus("1");
            log.info("小雨点 lendingOrderInfo end xydOrderDetailInfoDto:{} 入参：{}", JSONObject.toJSONString(xydOrderDetailInfoDto), JSONObject.toJSONString(xydLendingOrderInfoReq));

            return xydOrderDetailInfoDto;
        } catch (Exception e) {
            log.error("###小雨点放款获取订单信息异常, param:{}", JSONObject.toJSONString(xydLendingOrderInfoReq));

            alarmService.asyncAlarm("小雨点放款获取订单信息异常", "lendingOrderInfo",
                    e.getMessage(), JSONObject.toJSONString(xydLendingOrderInfoReq));
        }

        return null;
    }

    @SneakyThrows
    @Override
    public boolean notifyRepaymentResults(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq) {
        log.info("notifyRepaymentResults req:{}", JSON.toJSONString(xydNotifyRepaymentResultsReq));
        checkNotifyRepaymentResultsParam(xydNotifyRepaymentResultsReq);

        try {
            XydNotifyTypeEnum notifyTypeEnum = XydNotifyTypeEnum.fromCode(xydNotifyRepaymentResultsReq.getNotifyType());
            if (notifyTypeEnum == null) {
                throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "通知类型错误");
            }

            switch (notifyTypeEnum) {
                case REFUND:
                    // 退款通知
                    processXydCreditRefundNotify(xydNotifyRepaymentResultsReq);
                    break;
                case REPAY:
                    // 还款通知
                    processXydCreditRepaymentNotify(xydNotifyRepaymentResultsReq);
                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            log.error("小雨点通知还款结果异常， param:{}", JSON.toJSONString(xydNotifyRepaymentResultsReq), e);

            alarmService.asyncAlarm("小雨点通知还款结果异常", "notifyRepaymentResults",
                    e.getMessage(), JSON.toJSONString(xydNotifyRepaymentResultsReq));
        }

        return true;
    }

    /**
     * 通知还款结果-更新用户可用额度
     *
     * @param resultsReq
     */
    private void processXydCreditRepaymentNotify(XydNotifyRepaymentResultsReq resultsReq) throws PaymentException {
        log.info("小雨还款通知修改三方白名单可用额度：merchantId:{}", JSON.toJSONString(resultsReq));

        PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(resultsReq.getThirdOrderNo());
        if (prePayOrder == null || StringUtils.isBlank(prePayOrder.getPayer())) {
            throw new PaymentException(ResultCodeEnum.XYDLOAN_PAYER_ERROR, "通知类型错误");
        }

        // 更新or新增还款明细
        List<XydRepayPlanDetailPo> detailPos = repayPlanDetailMapper.selectByPayNo(resultsReq.getThirdOrderNo());
        RefundOrder refundOrder = null;
        if (StringUtils.isNotBlank(resultsReq.getSequenceNumber()) && XydNotifyTypeEnum.REFUND.getCode().equals(resultsReq.getNotifyType())) {
            refundOrder = refundDomainMapper.selectByRefundNo(resultsReq.getSequenceNumber());
        }
        Map<String, XydRepayPlanDetailPo> flowNoMap = detailPos.stream().collect(Collectors.toMap(d -> d.getFlowNo(), d -> d, (o, n) -> o));
        XydRepayPlanDetailPo insertDetailPo = null, updateDetailPo = null;
        XydRepayPlanDetailPo detailPo = flowNoMap.get(resultsReq.getSerialNumber());
        if (detailPo == null) {
            insertDetailPo = buildInsertDetailPo(resultsReq, refundOrder);
            flowNoMap.put(insertDetailPo.getFlowNo(), insertDetailPo);
        } else {
            updateDetailPo = buildUpdateDetailPo(detailPo, resultsReq, refundOrder);
            flowNoMap.put(updateDetailPo.getFlowNo(), updateDetailPo);
        }

        // 查询放款结果 更新还款借据
        XydLoanResultDto resultDto = xydLoanService.searchLoanResult(prePayOrder.getPayNo(), prePayOrder.getPayer());
        XydRepayPlanPo repayPlanPo = repayPlanMapper.selectByPayNo(prePayOrder.getPayNo());

        XydRepayPlanPo updatePlanPo = buildUpdatePlanPo(resultDto, repayPlanPo, flowNoMap);

        // 持久化数据
        if (insertDetailPo != null) {
            repayPlanDetailMapper.insert(insertDetailPo);
        }
        if (updateDetailPo != null) {
            repayPlanDetailMapper.updateByPrimaryKeySelective(updateDetailPo);
        }
        repayPlanMapper.updateByPrimaryKeySelective(updatePlanPo);
        pingAnAccountApi.updateAvailableAmountByChannel(Long.parseLong(prePayOrder.getPayer()), FinanceChannelEnum.XYD_LOAN.getCode());
    }

    private XydRepayPlanPo buildUpdatePlanPo(XydLoanResultDto resultDto, XydRepayPlanPo repayPlanPo, Map<String, XydRepayPlanDetailPo> flowNoMap) {
        XydRepayPlanPo updatePlanPo = new XydRepayPlanPo();
        updatePlanPo.setId(repayPlanPo.getId());
        updatePlanPo.setInterest(resultDto.getInterest());
        updatePlanPo.setCreditRate(NumberUtil.str2decimalZero(resultDto.getCreditRate(), 2));
        updatePlanPo.setLoanLimit(resultDto.getLoanLimit());
        if (CollUtil.isNotEmpty(resultDto.getPlans())) {
            XydRepaymentPlanDto repaymentPlanDto = resultDto.getPlans().get(0);
            updatePlanPo.setDueDateActual(DateUtil.parase(repaymentPlanDto.getDueDateActual(), DateUtil.DATEFORMATDAY));
            updatePlanPo.setPaidOff(Boolean.TRUE.equals(repaymentPlanDto.getPaidOff()) ? 1 : 0);
            updatePlanPo.setPrincipalPaid(repaymentPlanDto.getPrincipalPaid());
            updatePlanPo.setPrincipal(repaymentPlanDto.getPrincipal());
            updatePlanPo.setInterest(repaymentPlanDto.getInterest());
            updatePlanPo.setInterestPaid(repaymentPlanDto.getInterestPaid());
            updatePlanPo.setLateFee(repaymentPlanDto.getLateFee());
            updatePlanPo.setLateFeePaid(repaymentPlanDto.getLateFeePaid());
            updatePlanPo.setDayInDefault(repaymentPlanDto.getDayInDefault());
        }

        BigDecimal subsidyInterest = BigDecimal.ZERO, repayPenalty = BigDecimal.ZERO;
        Integer subsidyDays = 0, isOriginalRoute = 0;
        Collection<XydRepayPlanDetailPo> detailPos = flowNoMap.values();
        for (XydRepayPlanDetailPo detailPo : detailPos) {
            if (XydStatusEnum.RECORDED.getCode().equals(detailPo.getStatus())) {
                if (detailPo.getSubsidyInterest() != null) {
                    subsidyInterest = subsidyInterest.add(detailPo.getSubsidyInterest());
                }
                if (detailPo.getRepayPenalty() != null) {
                    repayPenalty = repayPenalty.add(detailPo.getRepayPenalty());
                }
                if (detailPo.getSubsidyDays() != null) {
                    subsidyDays += detailPo.getSubsidyDays();
                }
            }
            if (XydNotifyTypeEnum.REFUND.getCode().equals(detailPo.getNotifyType())) {
                isOriginalRoute = 1;
            }
        }
        updatePlanPo.setSubsidyDays(subsidyDays);
        updatePlanPo.setSubsidyInterest(subsidyInterest);
        updatePlanPo.setRepayPenalty(repayPenalty);
        updatePlanPo.setIsOriginalRoute(isOriginalRoute);
        return updatePlanPo;
    }

    private XydRepayPlanDetailPo buildUpdateDetailPo(XydRepayPlanDetailPo detailPo, XydNotifyRepaymentResultsReq resultsReq, RefundOrder refundOrder) {
        XydRepayPlanDetailPo updateDetailPo = new XydRepayPlanDetailPo();
        updateDetailPo.setId(detailPo.getId());
        if (resultsReq.getRepayTime() != null) {
            updateDetailPo.setRepayTime(new Date(resultsReq.getRepayTime()));
        }
        updateDetailPo.setNotifyType(resultsReq.getNotifyType());
        if (XydNotifyTypeEnum.REFUND.getCode().equals(resultsReq.getNotifyType())) {
            updateDetailPo.setRefundPayNo(resultsReq.getSequenceNumber());
        }
        if (refundOrder != null) {
            updateDetailPo.setRefundNo(refundOrder.getBusinessRefundNo());
        }
        if (StringUtils.isNotBlank(resultsReq.getRepayLoanAmount())) {
            updateDetailPo.setRepayLoanAmount(new BigDecimal(resultsReq.getRepayLoanAmount()));
        }
        if (StringUtils.isNotBlank(resultsReq.getRepayInterest())) {
            updateDetailPo.setRepayInterest(new BigDecimal(resultsReq.getRepayInterest()));
        }
        if (StringUtils.isNotBlank(resultsReq.getSubsidyInterest())) {
            updateDetailPo.setSubsidyInterest(new BigDecimal(resultsReq.getSubsidyInterest()));
        }
        updateDetailPo.setSubsidyDays(resultsReq.getSubsidyDays());
        if (StringUtils.isNotBlank(resultsReq.getRepayPenalty())) {
            updateDetailPo.setRepayPenalty(new BigDecimal(resultsReq.getRepayPenalty()));
        }
        updateDetailPo.setRepaymentType(resultsReq.getRepaymentType());
        updateDetailPo.setPayNo(resultsReq.getThirdOrderNo());
        return updateDetailPo;
    }

    private XydRepayPlanDetailPo buildInsertDetailPo(XydNotifyRepaymentResultsReq resultsReq, RefundOrder refundOrder) {
        XydRepayPlanDetailPo detailPo = new XydRepayPlanDetailPo();
        detailPo.setFlowNo(resultsReq.getSerialNumber());
        if (resultsReq.getRepayTime() != null) {
            detailPo.setRepayTime(new Date(resultsReq.getRepayTime()));
        }
        detailPo.setNotifyType(resultsReq.getNotifyType());
        if (XydNotifyTypeEnum.REFUND.getCode().equals(resultsReq.getNotifyType())) {
            detailPo.setRefundPayNo(resultsReq.getSequenceNumber());
        }
        if (refundOrder != null) {
            detailPo.setRefundNo(refundOrder.getBusinessRefundNo());
        }
        if (StringUtils.isNotBlank(resultsReq.getRepayLoanAmount())) {
            detailPo.setRepayLoanAmount(new BigDecimal(resultsReq.getRepayLoanAmount()));
        }
        if (StringUtils.isNotBlank(resultsReq.getRepayInterest())) {
            detailPo.setRepayInterest(new BigDecimal(resultsReq.getRepayInterest()));
        }
        if (StringUtils.isNotBlank(resultsReq.getSubsidyInterest())) {
            detailPo.setSubsidyInterest(new BigDecimal(resultsReq.getSubsidyInterest()));
        }
        detailPo.setSubsidyDays(resultsReq.getSubsidyDays());
        if (StringUtils.isNotBlank(resultsReq.getRepayPenalty())) {
            detailPo.setRepayPenalty(new BigDecimal(resultsReq.getRepayPenalty()));
        }
        detailPo.setRepaymentType(resultsReq.getRepaymentType());
        detailPo.setStatus(XydStatusEnum.RECORDED.getCode());
        detailPo.setPayNo(resultsReq.getThirdOrderNo());
        return detailPo;
    }

    private void processXydCreditRefundNotify(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq) throws PaymentException {
        log.info("###小雨点退款通知 processXydCreditRefundNotify param:{}", JSON.toJSONString(xydNotifyRepaymentResultsReq));
        checkProcessXydCreditRefundNotifyParam(xydNotifyRepaymentResultsReq);

        XydNotifyRepaymentOptStatus xydNotifyRepaymentOptStatus = XydNotifyRepaymentOptStatus.fromCode(xydNotifyRepaymentResultsReq.getOptStatus());
        if (xydNotifyRepaymentOptStatus == null) {
            log.error("小雨点退款通知 操作结果错误, {}-{}-{}",
                    xydNotifyRepaymentResultsReq.getThirdOrderNo(),
                    xydNotifyRepaymentResultsReq.getSequenceNumber(),
                    xydNotifyRepaymentResultsReq.getOptStatus());
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "操作结果错误");
        }

        // lock
        String refundNotifyLockKey = RedisUtil.XYD_LOAN_REFUND_NOTIFY + xydNotifyRepaymentResultsReq.getSequenceNumber();
        if (!redisUtil.setNx(refundNotifyLockKey, "1", 10)) {
            log.error("###小雨点退款通知 get refundNotifyLock fail, notifyParam={}", JSON.toJSONString(xydNotifyRepaymentResultsReq));
            return;
        }

        RefundOrder refundOrder = refundOrderService.selectByRefundNo(xydNotifyRepaymentResultsReq.getSequenceNumber());
        if (refundOrder == null) {
            log.error("###小雨点退款通知 refundOrder not exist, {}", xydNotifyRepaymentResultsReq.getSequenceNumber());

            alarmService.asyncAlarm("小雨点退款通知异常", "processXydCreditRefundNotify",
                    "退款单不存在", xydNotifyRepaymentResultsReq.getSequenceNumber());
            return;
            // throw new PaymentException(ResultCodeEnum.ORDER_NOT_EXIST);
        }

        // 幂等
        if (Objects.equals(refundOrder.getStatus(), RefundOrder.STATUS_FINISH)) {
            log.error("###小雨点退款通知 idempotent {}", refundOrder.getRefundNo());
            return;
        }

        RefundDO refundDO = buildRefundDO(xydNotifyRepaymentResultsReq, xydNotifyRepaymentOptStatus);

        // 更新退款单
        processXydCreditRefundOrderUpdate(refundOrder, refundDO);

        PayResultOrder payResultOrder = payResultOrderMapper.selectByPayNoAndChannel(refundOrder.getPayNo(), refundOrder.getChannel());

        if (Boolean.TRUE.equals(refundDO.getSucceed())) {
            // 异步回调业务侧
            Long unixTime = refundOrder.getSuccessTime().getTime() / 1000;
            callbackRunner.callback(refundDO.getRefundNo(), CallbackResultVo.TYPE_REFUND_SUCCESS, refundOrder.getAmount(), unixTime, refundOrder.getCallbackAddress(), payResultOrder.getMerchantId(), refundOrder.getBusinessRefundNo());

            // 退款结算
            RechargeHandleDto rechargeHandleDto = RechargeHandleDto.builder().
                    rechargeType(RechargeHandleDto.REFUND).
                    channel(refundOrder.getChannel()).
                    orderNo(refundOrder.getRefundNo()).
                    accountId(refundOrder.getUserId()).
                    build();
            callbackRunner.syncSendMsgWithTag(RocketMqTopic.TOPIC_RECHARGE_HANDLE, RocketMqTag.TOPIC_RECHARGE_HANDLE_XYD_CREDIT_REFUND_SETTLE_TAG, rechargeHandleDto);
        } else {
            String failMsg = refundDO.getFailureMsg();
            // 异步回调
            callbackRunner.callback(refundDO.getRefundNo(), CallbackResultVo.TYPE_REFUND_FAIL, refundOrder.getAmount(), 0L, refundOrder.getCallbackAddress(), payResultOrder.getMerchantId(), refundOrder.getBusinessRefundNo(), failMsg);
        }

        PoolUtil.ALARM_POOL.execute(NamedTask.createRun(() -> {
            try {
                processXydCreditRepaymentNotify(xydNotifyRepaymentResultsReq);
            } catch (Exception e) {
                log.error("refund processXydCreditRepaymentNotify error:{}", JSON.toJSONString(xydNotifyRepaymentResultsReq), e);
                alarmService.asyncAlarm("小雨点退款-更新还款计划", "processXydCreditRepaymentNotify", "更新失败", xydNotifyRepaymentResultsReq.getSequenceNumber());
            }
        }, "小雨点退款更新还款计划" + xydNotifyRepaymentResultsReq.getSerialNumber()));

    }

    private void processXydCreditRefundOrderUpdate(RefundOrder refundOrder, RefundDO refundDO) {
        refundOrder.setStatus(refundDO.getStatus());
        refundOrder.setChannelTransactionNo(refundDO.getChannelTransactionNo());

        if (Boolean.TRUE.equals(refundDO.getSucceed())) {
            refundOrder.setSuccessTime(refundDO.getTimeSucceed());
        }

        int count = refundDomainMapper.updateStatusByRefundNo(refundOrder.getRefundNo(), refundOrder.getChannelRefundNo(), refundOrder.getSuccessTime(), new Date(), refundOrder.getStatus(), refundOrder.getChannelTransactionNo(), refundOrder.getVersion());
        if (count < 1) {
            log.error("###小雨点退款通知 processXydCreditRefundNotify update refundOrder error, refundOrder:{}", JSON.toJSONString(refundOrder));
            // alarm
            DingTalkMessage msg = DingTalkMessage.builder()
                    .type("小雨点退款回调")
                    .method("processXydCreditRefundOrderUpdate")
                    .msg("更新退款单失败")
                    .param("refundNo:" + refundOrder.getRefundNo()).build();
            alarmService.alarm(msg);
        }
    }

    private RefundDO buildRefundDO(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq, XydNotifyRepaymentOptStatus xydNotifyRepaymentOptStatus) {
        RefundDO refundDO = new RefundDO();
        refundDO.setRefundNo(xydNotifyRepaymentResultsReq.getSequenceNumber());
        refundDO.setSucceed(xydNotifyRepaymentOptStatus == XydNotifyRepaymentOptStatus.SUCCESS);
        refundDO.setPayNo(xydNotifyRepaymentResultsReq.getThirdOrderNo());
        refundDO.setStatus(xydNotifyRepaymentOptStatus == XydNotifyRepaymentOptStatus.SUCCESS ? RefundOrder.STATUS_FINISH : RefundOrder.STATUS_FAIL);
        refundDO.setChannelTransactionNo(xydNotifyRepaymentResultsReq.getSerialNumber());

        if (Boolean.TRUE.equals(refundDO.getSucceed())) {
            Date timeSucceed = getRefundSucceedTime(xydNotifyRepaymentResultsReq);
            refundDO.setTimeSucceed(timeSucceed);
        } else {
            refundDO.setFailureMsg(xydNotifyRepaymentResultsReq.getMessage());
        }

        return refundDO;
    }

    private Date getRefundSucceedTime(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq) {
        Long repayTime = xydNotifyRepaymentResultsReq.getRepayTime();
        Date timeSucceed = null;
        if (repayTime == null) {
            log.error("小雨点退款成功时间为null, {} {}", xydNotifyRepaymentResultsReq.getSequenceNumber(), xydNotifyRepaymentResultsReq.getThirdOrderNo());
        } else {
            timeSucceed = TimestampConverter.safeConvert(repayTime);
        }

        if (timeSucceed == null) {
            timeSucceed = new Date();
        }

        return timeSucceed;
    }

    private void checkProcessXydCreditRefundNotifyParam(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq) throws PaymentException {
        if (StringUtils.isBlank(xydNotifyRepaymentResultsReq.getOptStatus()) ||
                StringUtils.isBlank(xydNotifyRepaymentResultsReq.getThirdOrderNo()) ||
                StringUtils.isBlank(xydNotifyRepaymentResultsReq.getSequenceNumber())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "退款通知参数错误");
        }
    }

    private void checkNotifyRepaymentResultsParam(XydNotifyRepaymentResultsReq xydNotifyRepaymentResultsReq) throws PaymentException {
        if (xydNotifyRepaymentResultsReq.getNotifyType() == null ||
                StringUtils.isBlank(xydNotifyRepaymentResultsReq.getThirdOrderNo()) ||
                StringUtils.isBlank(xydNotifyRepaymentResultsReq.getSerialNumber()) ||
                StringUtils.isBlank(xydNotifyRepaymentResultsReq.getXydLoanNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }

    }

}
